#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include "queue.h"
#include "lock.h"
#include "rw_lock.h"

#define THREADS_COUNT 128
my_lock main_mutex;
my_lock mutex;
my_rwlock rwlock;

void FillQueue(queue_t *pqueue, const unsigned int size) {
	assert(pqueue);
	unsigned int i = 0;
	for (i = 0; i < size; ++i)
		QueuePush(pqueue, i);//rand());	
}


void *ThreadFunctionMainMutex(void *args) {
	lock_lock(&main_mutex);
	
	queue_t *pqueue = (queue_t *)args;
	assert(pqueue);
	// Make snapshot
	queue_t *psnapshot = MakeSnapshot(pqueue);	

	const unsigned int tid = 
	    (unsigned int) pthread_self() % (QUEUE_SIZE / THREADS_COUNT - 1);
//	printf("Thread id = [%d]\nQueue before:\n", tid);
//	PrintQueue(pqueue);
	
	// Pop tid elements
	queue_elem_t *elem_array =
	    (queue_elem_t *)calloc(tid, sizeof(*elem_array));
	assert(elem_array);
	int i = 0;
	for (i = 0; i < tid; ++i)
		elem_array[i] = QueuePop(pqueue);

	// Process elements
	int j = 0;
	for (i = 0; i < tid; ++i)
		for(j = 0; j < tid; ++j) {
			queue_elem_t temp = elem_array[i];
			elem_array[i] = elem_array[j];
			elem_array[j] = temp;
		}

	// Push them back into queue
	for (i = tid - 1; i >= 0; --i)
		QueuePush(pqueue, elem_array[i]);

//	printf("Thread id = [%d]\n Queue after:\n", tid);
//	PrintQueue(pqueue);

	lock_unlock(&main_mutex);
	
	
	free(elem_array);	
	QueueDestroy(psnapshot);

	return NULL;
}

void *ThreadFunctionGranular(void *args) {
	
	queue_t *pqueue = (queue_t *)args;
	assert(pqueue);
	// Make snapshot
	lock_lock(&mutex);
	queue_t *psnapshot = MakeSnapshot(pqueue);	
	lock_unlock(&mutex);

	const unsigned int tid = 
	    (unsigned int) pthread_self() % (QUEUE_SIZE / THREADS_COUNT - 1);
//	printf("Thread id = [%d]\nQueue before:\n", tid);
//	PrintQueue(pqueue);
	
	// Pop tid elements
	queue_elem_t *elem_array =
	    (queue_elem_t *)calloc(tid, sizeof(*elem_array));
	assert(elem_array);
	int i = 0;
	for (i = 0; i < tid; ++i) {
		lock_lock(&mutex);
		elem_array[i] = QueuePop(pqueue);
		lock_unlock(&mutex);
	}

	// Process elements
	int j = 0;
	for (i = 0; i < tid; ++i)
		for(j = 0; j < tid; ++j) {
			queue_elem_t temp = elem_array[i];
			elem_array[i] = elem_array[j];
			elem_array[j] = temp;
		}

	// Push them back into queue
	for (i = tid - 1; i >= 0; --i) {
		lock_lock(&mutex);
		QueuePush(pqueue, elem_array[i]);
		lock_unlock(&mutex);
	}

//	printf("Thread id = [%d]\n Queue after:\n", tid);
//	PrintQueue(pqueue);
	
	
	free(elem_array);	
	QueueDestroy(psnapshot);

	return NULL;
}

void *ThreadFunctionRWLock(void *args) {
	
	queue_t *pqueue = (queue_t *)args;
	assert(pqueue);
	// Make snapshot
	rwlock_read(&rwlock);
	queue_t *psnapshot = MakeSnapshot(pqueue);	
	rwunlock_read(&rwlock);

	const unsigned int tid = 
	    (unsigned int) pthread_self() % (QUEUE_SIZE / THREADS_COUNT - 1);
//	printf("Thread id = [%d]\nQueue before:\n", tid);
//	PrintQueue(pqueue);
	
	// Pop tid elements
	queue_elem_t *elem_array =
	    (queue_elem_t *)calloc(tid, sizeof(*elem_array));
	assert(elem_array);
	int i = 0;
	for (i = 0; i < tid; ++i) {
		rwlock_write(&rwlock);
		elem_array[i] = QueuePop(pqueue);
		rwunlock_write(&rwlock);
	}

	// Process elements
	int j = 0;
	for (i = 0; i < tid; ++i)
		for(j = 0; j < tid; ++j) {
			queue_elem_t temp = elem_array[i];
			elem_array[i] = elem_array[j];
			elem_array[j] = temp;
		}

	// Push them back into queue
	for (i = tid - 1; i >= 0; --i) {
		rwlock_write(&rwlock);
		QueuePush(pqueue, elem_array[i]);
		rwunlock_write(&rwlock);
	}

//	printf("Thread id = [%d]\n Queue after:\n", tid);
//	PrintQueue(pqueue);
	
	
	free(elem_array);	
	QueueDestroy(psnapshot);

	return NULL;
}

void RunTask(queue_t *pqueue, void* (*func) (void*)) {
	clock_t beg = clock();
	assert(pqueue);
	assert(func);
	pthread_t threads[THREADS_COUNT] = {0};
	unsigned int i = 0;
	for(i = 0; i < THREADS_COUNT; ++i)
		assert(!pthread_create(threads + i, NULL, func, (void*)pqueue));

	for(i = 0; i < THREADS_COUNT; ++i)
		assert(!pthread_join(threads[i], NULL));	
	printf("time elapsed = %lld\n", clock() - beg);

}


int main() {
	lock_init(&main_mutex);
	lock_init(&mutex);
	rwlock_init(&rwlock);
	queue_t *pqueue = QueueInit(QUEUE_SIZE);
	FillQueue(pqueue, QUEUE_SIZE);

	printf("Main mutex: ");
	RunTask(pqueue, ThreadFunctionMainMutex);	
	printf("Granular: ");
	RunTask(pqueue, ThreadFunctionGranular);	
	printf("RWLock: ");
	RunTask(pqueue, ThreadFunctionRWLock);	
	

	QueueDestroy(pqueue);

	return 0;
}

